package com.framework.core.log.aop;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.framework.core.common.enums.LogStatusEnum;
import com.framework.core.log.annotation.AutoLog;
import com.framework.core.log.entity.dto.SysLogDTO;
import com.framework.core.util.AddressUtil;
import com.framework.core.util.IpUtil;
import com.framework.core.util.SecurityUtil;
import com.framework.system.service.ISysLogService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 日志注解
 *
 * @author Kevin
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Pointcut("@annotation(com.framework.core.log.annotation.AutoLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    @SneakyThrows
    public Object around(ProceedingJoinPoint point) {
        Long startTime = System.currentTimeMillis();
        Object obj = null;
        Exception exception = null;
        try {
            obj = point.proceed();
        } catch (Exception e) {
            exception = e;
            throw e;
        } finally {
            Long endTime = System.currentTimeMillis();
            handleLog(point, exception, obj, endTime - startTime);
        }
        return obj;
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object retValue, long time) {
        try {
            // 获得注解
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            AutoLog controllerLog = AnnotationUtils.findAnnotation(methodSignature.getMethod(), AutoLog.class);
            if (Objects.isNull(controllerLog)) {
                return;
            }
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            SysLogDTO sysLog = new SysLogDTO();
            sysLog.setLogModule(controllerLog.module());
            sysLog.setLogTitle(controllerLog.title());
            sysLog.setLogStatus(LogStatusEnum.SUCCESS.getCode());
            sysLog.setUserPlatform(controllerLog.userPlatform().getCode());
            sysLog.setRequsetUri(request.getRequestURI());
            sysLog.setRequsetType(request.getMethod());
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            sysLog.setRequsetMethod(className + "." + methodName + "()");

            if (controllerLog.isSaveRequestData()) {
                List<Object> objectList = Arrays.stream(joinPoint.getArgs()).filter(item -> Objects.nonNull(item) && !isFilterObject(item)).collect(Collectors.toList());
                sysLog.setRequsetParams(JSONUtil.toJsonStr(objectList));
            }
            if (controllerLog.isSaveResponseData()) {
                sysLog.setResponseResult(StrUtil.sub(JSONUtil.toJsonStr(retValue), 0, 5000));
            }

            sysLog.setRequsetTime(String.valueOf(time));

            String ipAddress = IpUtil.getClientIp(request);
            sysLog.setIpAddress(ipAddress);
            sysLog.setOperLocation(AddressUtil.getRealAddressByIp(ipAddress));
            UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
            sysLog.setBrowser(userAgent.getBrowser().getName());
            sysLog.setOs(userAgent.getOs().getName());

            if (StpUtil.isLogin()) {
                sysLog.setOperName(SecurityUtil.getUsername());
            }
            if (e != null) {
                sysLog.setLogStatus(LogStatusEnum.FAILURE.getCode());
                sysLog.setException(e.getMessage());
            }
            ThreadUtil.execAsync(() -> {
                ISysLogService sysLogService = SpringUtil.getBean(ISysLogService.class);
                sysLogService.insertSysLog(sysLog);
            });
            log.debug("URI：{}，模块：{}，功能：{}，执行耗时：{}", sysLog.getRequsetUri(), sysLog.getLogModule(), sysLog.getLogTitle(), time >= 1000 ? time / 1000 + "s" : time + "ms");
        } catch (Exception exception) {
            log.error("异常信息:{}", exception.getMessage());
            exception.printStackTrace();
        }
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult || o instanceof Page;
    }
}
